package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"io"
	"log"
	"os"
	"protobuf-learn/proto"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

func helloServiceClient(ctx context.Context, conn *grpc.ClientConn) error {
	// 添加meta
	md := metadata.Pairs("x-request-id", "123123123")
	ctx = metadata.NewOutgoingContext(ctx, md)

	var (
		headers  = metadata.New(map[string]string{})
		trailers = metadata.New(map[string]string{})
	)

	client := proto.NewHelloServiceClient(conn)
	res, err := client.SayHello(
		ctx,
		&proto.SayHelloRequest{Name: "delong"},
		grpc.Header(&headers),
		grpc.Trailer(&trailers),
		// grpc.MaxCallRecvMsgSize(10),
		// grpc.MaxCallSendMsgSize(5),
	)
	if err == nil {
		log.Printf("response received: %s", res.Message)
		log.Printf("headers: %s", headers)
		log.Printf("trailers: %s", trailers)
		return nil
	}

	return err
}

func todoServiceClient(ctx context.Context, conn *grpc.ClientConn) error {
	client := proto.NewTodoServiceClient(conn)

	res1, err := client.AddTask(ctx, &proto.AddTaskRequest{
		Task: "this is a the first task",
	})
	if err != nil {
		return err
	}
	log.Printf("response received: %s \n", res1.GetId())

	res2, err := client.AddTask(ctx, &proto.AddTaskRequest{
		Task: "this is a the second task",
	})
	if err != nil {
		return err
	}

	log.Printf("response received: %s \n", res2.GetId())

	res3, err := client.ListTasks(ctx, &proto.ListTasksRequest{})
	if err != nil {
		return err
	}
	log.Printf("response received: %v \n", res3.GetTasks())

	_, err = client.CompleteTask(ctx, &proto.CompleteTaskRequest{
		Id: res1.GetId(),
	})
	if err != nil {
		return err
	}

	res4, err := client.ListTasks(ctx, &proto.ListTasksRequest{})
	if err != nil {
		return err
	}
	log.Printf("response received: %v \n", res4.GetTasks())

	return nil
}

func streamingServiceClient(ctx context.Context, conn *grpc.ClientConn) error {
	client := proto.NewStreamingServiceClient(conn)

	res, err := client.StreamServerTime(ctx, &proto.StreamServerTimeRequest{
		IntervalSeconds: 10,
	})
	if err == nil {
		log.Printf("response received: %v", res)
		for {
			res, err := res.Recv()
			if err == io.EOF {
				break
			}
			if err != nil {
				log.Fatal(err)
			}

			log.Printf("received time from server: %s", res.CurrentTime.AsTime())
		}

		log.Println("server stream closed")
		return nil
	}

	return err
}

func main() {
	ctx := context.Background()

	// load tls from privite CA
	certPool := x509.NewCertPool()
	cert, err := os.ReadFile("certs/ca.crt")
	if err != nil {
		log.Fatal(err)
	}
	if ok := certPool.AppendCertsFromPEM(cert); !ok {
		log.Fatal("failed to append CA cert")
	}

	clientCert, err := tls.LoadX509KeyPair("certs/client.crt", "certs/client.key")
	if err != nil {
		log.Fatal(err)
	}

	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{clientCert},
		RootCAs:      certPool,
	}

	tlsCredentials := credentials.NewTLS(tlsConfig)

	host := ":50051"
	conn, err := grpc.NewClient(host,
		//grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithTransportCredentials(tlsCredentials),
		// grpc.WithUnaryInterceptor(
		// 	func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
		// 		log.Printf("sending request: %s", method)
		// 		err := invoker(ctx, method, req, reply, cc, opts...)
		// 		log.Printf("response received from server: %s", method)
		// 		return err
		// 	}),
		grpc.WithChainUnaryInterceptor(
			func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
				start := time.Now()
				err := invoker(ctx, method, req, reply, cc, opts...)
				log.Printf("response %s took %s", method, time.Since(start))
				return err
			},
			func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
				log.Printf("sending request: %s", method)
				err := invoker(ctx, method, req, reply, cc, opts...)
				log.Printf("response received from server: %s", method)
				return err
			},
		),
		grpc.WithChainStreamInterceptor(
			func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
				start := time.Now()
				clientStream, err := streamer(ctx, desc, cc, method, opts...)
				log.Printf("stream response %s took %s", method, time.Since(start))
				return clientStream, err
			},
			func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
				log.Printf("sending request: %s", method)
				clientStream, err := streamer(ctx, desc, cc, method, opts...)
				log.Printf("response received from server: %s", method)
				return clientStream, err
			},
		),
	)

	if err != nil {
		log.Fatal(err)
	}

	defer conn.Close()

	err = helloServiceClient(ctx, conn)

	//err = todoServiceClient(ctx, conn)
	//err = streamingServiceClient(ctx, conn)
	if err == nil {
		return
	}
	st, ok := status.FromError(err)
	if ok {
		log.Fatalf("status code: %s, error: %s", st.Code().String(), st.Message())
		return
	}
	log.Fatalln(err)
}
